<?
/*

  This file is part of Kato Javascript Framework (http://kato.googlecode.com/)

  Copyright (c) 2007, Filip Dreger, Jan Urbański

  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above
        copyright notice, this list of conditions and the following
        disclaimer in the documentation and/or other materials provided
        with the distribution.
      * Neither the name of Kato Javascript Framework nor the names
        of its contributors may be used to endorse or promote products
        derived from this software without specific prior written
        permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

DbConn::log('start_df');
class DefaultView {
	protected $dependent_markers;
	protected $viewname;
	protected $columns;
	protected $columns_by_name;
	protected $uploads;
	protected $sql = array(
		'select' => 'select',
		'fields' => '*',
		'from' => 'from',
		'table' => '',
		'where command' => '',
		'where' => '',
		'order by command' => 'order by',
		'order by' => '',
		'offset_command' => '',
		'offset' => '',
		'limit_command' => '',
		'limit' => '',
	);
	protected $id_columns;

	public $filename = 'file';
	public $params = array();
	public $filename_column;
	public $offset = 0;
	public $orderby = true;
	public $search;
	public $id;
	public $limit = 999999;	// :-P


	function prepare_id_columns(){
		if (!$this->id_columns){
			$this->id_columns = array($this->columns[0]['column']);
		}
	}

	function prepare_columns_by_name(){
		foreach ($this->columns as $i=>$c){
			$this->columns_by_name[$c['column']] = $i;
		}
	}

	function create_where_for_operation($row){
		$sql = ' where true  ';
		foreach($this->id_columns as $column){
			$sql .=  " and $column = '{$row[$this->columns_by_name[$column]]}' ";
		}
		return $sql;
	}

	function __construct($viewname){
		$this->viewname = $viewname;
		$rc = new ReflectionClass($this);

		$properties = $rc->getProperties();
		foreach ($properties as $property){
			$property_name = $property->name;
			if ($property->isPublic() && isset($_REQUEST[$property_name])){
				$this->{$property_name} = $_REQUEST[$property_name];
			}
		}

		$this->columns = $this->get_columns($this->viewname);
		$this->prepare_id_columns();
		$this->prepare_columns_by_name();

		if (isset($_REQUEST['update_markers'])){
			$this->update_markers();
			return;
		}
		
		if(isset($_REQUEST['operations']) && $_REQUEST['operations']){
			$this->process_rows($_REQUEST['operations']);
			return;
		}
		
		if(isset($_REQUEST['download'])){
			if(!preg_match('/^[a-z0-9]+$/i',$_REQUEST['download'])){
				exit;
			}
			global $config;
			$filepath = $config['uploads'] . '/' . $this->viewname . '/' . $_REQUEST['download'];
			header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
			header("Content-type: application/force-download");
			header("Content-Length: " . filesize($filepath));
			header("Pragma: public");
			header("Content-disposition: attachment; filename=\"".$this->filename."\";");
			header("Expires: 0"); 
			readfile($filepath);
			exit;
		}

		$sql = $this->create_sql();
		$this->rows = $this->get_rows($sql,$this->params);

		$this->set = array(
			'head' => array(),
			'columns' => $this->columns,
			'rows' => $this->rows
		);
		
		global $config;
		$revision = @(int)file_get_contents($config['marker_dir'] . '/' . $this->viewname);
		$this->set['head']['revision'] = $revision;

                $this->before_set_rendered();
		$this->render_set($this->set);
	}

        function before_set_rendered(){

        }

	function process_rows($operations){
		global $config;
		$j = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
		$operations=$j->decode($operations);

		foreach ($operations as $o){
			echo $this->process_row($o);
		}
		
		if($this->uploads) $this->process_uploads();
		$this->update_markers();
	}
	
	function update_markers(){
		global $config;
		if($this->dependent_markers){
			foreach($this->dependent_markers as $name){
				file_put_contents($config['marker_dir'] . "/" . $name ,time());
			}
		} else {
			file_put_contents($config['marker_dir'] . "/" . $this->viewname,time());
		}
	}
	
	function process_uploads(){
		global $config;
		$dir = $config['uploads'];
		$temp = $config['temporary_uploads'] . '/' . session_id();
	
		foreach ($this->uploads as $upload){
			$this->process_upload($upload);
		}
	}
	
	function process_upload($upload){
	        global $config;
		$dir = $config['uploads'];
		$temp = $config['temporary_uploads'] . '/' . session_id();
	        $source = $temp . '/' . $upload['filename'];
		$target = $dir . '/' . $upload['view'] . '/' . $upload['file_id'];
		@mkdir($dir . '/' . $upload['view']);
		DbConn::log($source . ' => ' . $target);
		rename($source, $target);
        }

	function process_row($p){
		$sql = "";
		$id_column = $this->id_columns[0];
		$upload = false;
		switch($p['command']){
			case 'delete':
				$sql .= "delete from {$this->viewname} ";
				$sql .= $this->create_where_for_operation($p['row']);
				break;
			case 'update':
				$sql .= "update {$this->viewname} set";
				$sets = array();
				foreach($p['row'] as $index=>$value){
					if ($index==0) continue;
					if ($this->columns[$index]['column'] == 'custom_columns') continue;
					if ($value===null){
						$value='null';
					} else {
						$value = "'" . pg_escape_string($value) . "'";
					}
					$sets[] = ' ' . $this->columns[$index]['realcolumn'] . '=' . $value;
				}
				$sql .= implode(",",$sets);
				$sql .= $this->create_where_for_operation($p['row']);
				break;
			case 'insert':
				$sql .= "insert into {$this->viewname} ";
				$cols = array();
				$values = array();
				foreach($p['row'] as $index=>$raw_value){
					if ($index==0) continue;
					if ($this->columns[$index]['column'] == 'custom_columns') continue;
					if ($raw_value===null){
						$value = $this->columns[$index]['has default'] ? 'default' : 'null';
					} else {
						$value = "'" . pg_escape_string($raw_value) . "'";
					}
					$cols[] = $this->columns[$index]['realcolumn'];
					$values[] = $value;
					if($index==$this->filename_column){
						$upload = array('view'=>$this->viewname, 'filename'=>$raw_value);
					}
				}
				$sql .= '(' . implode(',',$cols) . ') values (' . implode(',',$values) . ")";
				break;
		}
		if($upload){
			$sql .= " returning " . $id_column;
		}
		$id = DbConn::query($sql);
		if($upload){
			$upload['file_id']=$id[0][0];
			$this->uploads[] = $upload;
		}
		// moze trzeba tu cos zwracac, zeby Ajax nie czekal na odpowiedz?
	}

	function create_sql(){
		$this->sql['table'] = $this->viewname;
		if ($this->offset){
			$this->sql['offset'] = $this->offset;
			$this->sql['offset_command'] = 'offset';
		}
		if($this->params){
			// fixme
		}
		if ($this->search){
			$this->sql['where command'] = 'where ';
			foreach($this->search as $key => $val) {
				switch($key){
					case 'offset':
						$this->offset = $val;
						break;
					case 'limit':
						$this->limit = $val;
						break;
				}
				if (preg_match('/^([^_]*)_(.*)$/', $key, $searchspec)) {
					$searchspec[2] = $this->columns[$this->columns_by_name[$searchspec[2]]]['realcolumn'];
					DbConn::log(print_r($this->columns_by_name, TRUE));
					switch ($searchspec[1]) {
					case 'plaintotsquery':
						if(pg_escape_string(urldecode($val)))
						/*
						 * UWAGA tutaj trzeba sprawdzic, czy nasze tsquery nie zawiera samych stopwordow,
						 * bo inaczej Postgres zglosi blad, ze indeks GIN nie jest
						 * uzywalny z pusytm zapytaniem (a planer juz stworzyl plan, ktory uzywa indeksu)
						 * sprawdzenie jest robione przez case when <nasze_zapytanie> <@ to_tsquery('') then false else <dalej normalnie>,
						 * czysto techniczna sztuczka
						 * TO JEST BUG W POSTGRESIE (poprawiony w 8.3)
						 */
							$this->sql['where'] .=
								"case when plainto_tsquery('" . pg_escape_string(urldecode($val)) . "') <@ to_tsquery('') then false else " .
								($searchspec[2] . " @@ plainto_tsquery('" . pg_escape_string(urldecode($val)) . "') end and ");
						break;
	
					case 'search':
						if($val!='null')	// fixme: does json really turn null to 'null'? oh, the horror!
							$this->sql['where'] .= ($searchspec[2] . " = '" . pg_escape_string(urldecode($val)) . "' and ");
						else
							$this->sql['where'] .= 'false and ';
						break;
	
					case 'regexp':
						if($val!=null)
							$this->sql['where'] .= ($searchspec[2] . " ILIKE '%" . pg_escape_string(urldecode($val)) . "%' and ");
						break;

					case 'isnull':
						$this->sql['where'] .= ($searchspec[2] . " is " . ($val == 'true' ? "null" : "not null") . " and ");
						break;
						
					case 'ge':
						$this->sql['where'] .= ($searchspec[2] . " >= '" . pg_escape_string(urldecode($val)) . "' and ");
						break;

					case 'le':
						$this->sql['where'] .= ($searchspec[2] . " <= '" . pg_escape_string(urldecode($val)) . "' and ");
						break;
						
					case 'gt':
						$this->sql['where'] .= ($searchspec[2] . " > '" . pg_escape_string(urldecode($val)) . "' and ");
						break;
						
					case 'lt':
						$this->sql['where'] .= ($searchspec[2] . " < '" . pg_escape_string(urldecode($val)) . "' and ");
						break;

					// fixme: sql injection
						
					case 'orderby':
						$requested_order[] = ($searchspec[2] . ' ' . $val);
						break;
					}
				}
			}
			$this->sql['where'] .= 'true';
		}
		if (isset($this->limit) && $this->limit){
			// szutczka: jeżeli limit jest robiony przez serwer, to 
			// serwer próbuje zwrócić o jedną więcej linijkę.
			// dzięki temu po stronie klienta prawidłowo działa 'następna strona'
			$this->sql['limit'] = ($this->limit+1);
			$this->sql['limit_command'] = 'limit';
		}
		if ($this->offset){
			$this->sql['offset'] = $this->offset;
			$this->sql['offset_command'] = 'offset';
		}
		if (isset($requested_order)) {
			$this->sql['order by'] = implode(', ', $requested_order);
		}
		else {
			if($this->orderby){
				$this->sql['order by'] = $this->columns[0]['column'] . ' desc';
			} else {
				$this->sql['order by command'] = '';
			}
		}
		return implode(' ',$this->sql);
	}

	function get_columns($viewname){
		$meta = DbConn::get_meta_data($viewname);
		$columns = array();
		foreach($meta as $k=>$m){
			$m['column'] = $k;
			$m['realcolumn'] = $k;
			$columns[] = $m;
		}
		return $columns;
	}

	function get_rows($sql,$params){
		return DbConn::query($sql,$params);
	}

	function render_set($set){
		global $config;
		header('Content-type: text/plain; charset=utf-8');
		$s = new Services_JSON(0);
//		echo iconv($config['sql_ascii'],'utf-8',$s->encode($set));
		echo $s->encode($set);
	}
}
